package generate

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"chainmaker.org/chainmaker/smarteditor/generate/tool"
	"chainmaker.org/chainmaker/smarteditor/logger"
	"chainmaker.org/chainmaker/smarteditor/util"
	"golang.org/x/mod/modfile"
)

const (
	CodeDirectory   = "src"
	InvokeDirectory = "exec"

	TargetFileName = "main.go"
	GoModFileName  = "go.mod"
	GoSumFileName  = "go.sum"

	ImportPlaceholder    = "$imports"
	NamesPlaceholder     = "$names"
	ContractsPlaceholder = "$contracts"
	PkgPrefix            = "generate/"
)

var log = logger.Get()

// GenerateParam generate code param
type Param struct {
	Workspace     string
	ContractNames map[string]struct{}
}

// GenCrossContractInvokeCode generate cross contract invoke code
// copy all user contract from "workspace/src/" to "workspace/exec/" directory
// and generate run code
func GenCrossContractInvokeCode(param Param) error {
	sourcePath := filepath.Join(param.Workspace, CodeDirectory)
	destinationPath := filepath.Join(param.Workspace, InvokeDirectory)

	//copy file
	err := util.CopyDirInclude(sourcePath, destinationPath, param.ContractNames)
	if err != nil {
		log.Errorf("[GenerateCrossContractInvokeCode] copy directory fail, err:%v", err)
		return err
	}
	var subMods []*modfile.File
	//remove not select contract directory or remove [go.mod go.sum]
	dirList, err := ioutil.ReadDir(destinationPath)
	for i := range dirList {
		if _, ok := param.ContractNames[dirList[i].Name()]; !ok {
			err := os.RemoveAll(filepath.Join(destinationPath, dirList[i].Name()))
			if err != nil {
				log.Errorf("[GenerateCrossContractInvokeCode] remove not contract directory fail, err:%v", err)
				return err
			}
		} else {
			is210, checkErr := util.CheckContractIs210(filepath.Join(destinationPath, dirList[i].Name()))
			if checkErr != nil {
				log.Errorf("CheckContractIs210 %s failed %s",
					filepath.Join(destinationPath, dirList[i].Name()), checkErr.Error())
			}
			if is210 {
				return fmt.Errorf("检测到合约[%s]版本为v2.1.0+，请将IDE的长安链版本切换至v2.1.0+再进行操作", dirList[i].Name())
			}
			tempModFile := filepath.Join(destinationPath, dirList[i].Name(), GoModFileName)
			tempParserdMod, parseErr := util.ParseGoModFile(tempModFile)
			if parseErr == nil && tempParserdMod != nil {
				subMods = append(subMods, tempParserdMod)
			}
			err := os.RemoveAll(tempModFile)
			if err != nil {
				log.Errorf("[GenerateCrossContractInvokeCode] remove go.mod fail, err:%v", err)
				return err
			}
			err = os.RemoveAll(filepath.Join(destinationPath, dirList[i].Name(), GoSumFileName))
			if err != nil {
				log.Errorf("[GenerateCrossContractInvokeCode] remove go.sum fail, err:%v", err)
				return err
			}
			// 在这里加一个优化, 如果合约下面已经有编译后的文件了,删除
			tempExecFileName := filepath.Join(destinationPath, dirList[i].Name(), dirList[i].Name())
			removeExeErr := os.Remove(tempExecFileName)
			if removeExeErr != nil {
				log.Warnf("[GenerateCrossContractInvokeCode] exec file %s failed %+v",
					tempExecFileName, removeExeErr)
			}
		}
	}

	//read contract info
	contractInfoList, err := tool.ReadContractInfoListFromDirectory(destinationPath)
	if err != nil {
		log.Errorf("[GenerateCrossContractInvokeCode] read contract info fail, err:%v", err)
		return err
	}

	//rename contract package name
	for i := range contractInfoList.Data {
		log.Debugf("[GenerateCrossContractInvokeCode] index : %d, [%s]", i, contractInfoList.Data[i].String())
		err := tool.ReplaceMainPackageName(contractInfoList.Data[i].Path, contractInfoList.Data[i].DirectoryName)
		if err != nil {
			log.Errorf("[GenerateCrossContractInvokeCode] replace contract package name fail, err:%v", err)
			return err
		}

		old := tool.Quote + contractInfoList.Data[i].DirectoryName + tool.Slash
		replace := tool.Quote + PkgPrefix + contractInfoList.Data[i].DirectoryName + tool.Slash
		err = tool.ReplaceImportPackageName(contractInfoList.Data[i].Path, old, replace)
		if err != nil {
			log.Errorf("[GenerateCrossContractInvokeCode] replace import package name fail,"+
				" old:%s, replace:%s. err:%v", old, replace, err)
			return err
		}
		if contractInfoList.Data[i].DirectoryName == "" {
			log.Errorf("[GenerateCrossContractInvokeCode] replace contract package name, found empty directory name. contract info:%s", contractInfoList)
			return errors.New("found empty directory name")
		}
	}

	imports := strings.Builder{}
	importSplit := ""

	names := strings.Builder{}
	contracts := strings.Builder{}
	arraySplit := ""

	for _, contractInfo := range contractInfoList.Data {
		imports.WriteString(importSplit)
		imports.WriteString(tool.Quote)
		imports.WriteString(PkgPrefix)
		imports.WriteString(contractInfo.DirectoryName)
		imports.WriteString(tool.Quote)

		importSplit = "\n\t"

		names.WriteString(arraySplit)
		names.WriteString(tool.Quote)
		names.WriteString(contractInfo.DirectoryName)
		names.WriteString(tool.Quote)

		contracts.WriteString(arraySplit)
		contracts.WriteString("new(")
		contracts.WriteString(contractInfo.DirectoryName)
		contracts.WriteString(".")
		contracts.WriteString(contractInfo.ContractStructName)
		contracts.WriteString(")")
		arraySplit = ","
	}

	//replace file content
	newFileContent := strings.ReplaceAll(template, ImportPlaceholder, imports.String())
	newFileContent = strings.ReplaceAll(newFileContent, NamesPlaceholder, names.String())
	newFileContent = strings.ReplaceAll(newFileContent, ContractsPlaceholder, contracts.String())

	targetFilePath := filepath.Join(destinationPath, TargetFileName)
	err = ioutil.WriteFile(targetFilePath, []byte(newFileContent), 0644)
	if err != nil {
		log.Errorf("[GenerateCrossContractInvokeCode] read contract info fail, err:%v", err)
		return err
	}

	//add go mod file
	modFilePath := filepath.Join(destinationPath, GoModFileName)
	err = ioutil.WriteFile(modFilePath, []byte(goModTemplate), 0644)
	if err != nil {
		log.Errorf("[GenerateCrossContractInvokeCode] read contract info fail, err:%v", err)
		return err
	}
	//
	mainModFile, mainModFileErr := util.ParseGoModFile(modFilePath)
	if mainModFileErr != nil {
		log.Errorf("GenerateCrossContractInvokeCode ParseGoModFile %s failed %s", modFilePath, mainModFileErr.Error())
		return nil
	}
	for i := 0; i < len(subMods); i++ {
		util.MergeSubMod(mainModFile, subMods[i])
	}
	generateErr := util.GenerateNewModFile(mainModFile, modFilePath)
	if generateErr != nil {
		log.Errorf("GenerateCrossContractInvokeCode GenerateNewModFile %s failed %s", modFilePath, generateErr.Error())
		return generateErr
	}
	return nil
}
